{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# FLASK的应用结构和蓝图\r\n",
    "\r\n",
    "到目前为止，我们的整个 Flask 应用程序主要驻留在单个文件 main2.py 中。这对于小型应用程序来说没有问题，但是随着项目的增长，它变得难以管理。当我们将一个单片文件分解成多个文件时，其中的代码变得更易于维护和预测。\r\n",
    "\r\n",
    "FLASK不会对你应该如何构建应用程序施加任何限制。然而，它确实提供了一些使应用程序模块化的指导方针。\r\n",
    "\r\n",
    "在本课程中，我们将使用以下应用程序结构。\r\n",
    "\r\n",
    "```\r\n",
    "/app_dir\r\n",
    "    /app\r\n",
    "        __init__.py\r\n",
    "        /static\r\n",
    "        /templates\r\n",
    "        views.py\r\n",
    "    config.py\r\n",
    "    runner.py\r\n",
    "```\r\n",
    "\r\n",
    "以下是每个文件和文件夹的功能:\r\n",
    "\r\n",
    "| File 档案     | Description 描述                                             |\r\n",
    "| :------------ | :----------------------------------------------------------- |\r\n",
    "| `app_dir`     | 这个`app_dir` 是你的 Flask 项目的根目录 |\r\n",
    "| `app`         | 这个`app` 目录是一个 Python 包，它包含视图、模板和静态文件 |\r\n",
    "| `__init__.py` | 这个`__init__.py` 告诉 Python `app` 目录是一个 Python 包 |\r\n",
    "| `static`      | 这个`static` 目录包含项目的静态文件 |\r\n",
    "| `templates` | 这个`templates` 目录包含模板 |\r\n",
    "| `views.py`  | 这个`views.py` 包含路径和视图函数 |\r\n",
    "| `config.py` | 这个`config.py` 包含您的Flask应用程序的设置和配置 |\r\n",
    "| `runner.py` | 您的Flask应用程序的入口点 |\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 基于类的配置\r\n",
    "\r\n",
    "一个软件项目通常在三种不同的环境中运行:\r\n",
    "\r\n",
    "- 开发\r\n",
    "- 测试\r\n",
    "- 生产\r\n",
    "\r\n",
    "随着项目的发展，您将遇到需要为不同的环境指定不同的配置选项。您还会注意到，无论您处于哪个环境中，在某些配置中总是保持不变。我们可以使用类实现这样的配置系统。\r\n",
    "\r\n",
    "首先在基类中定义默认配置，然后创建从基类继承的特定于环境的类。特定于环境的类可以重写或添加特定于环境的配置。\r\n",
    "\r\n",
    "在 `flask_app` 目录中创建一个名为 `config.py` 的新文件，并在其中添加以下代码:\r\n",
    "\r\n",
    "flask_app/config.py\r\n",
    "\r\n",
    "```python\r\n",
    "import os\r\n",
    "\r\n",
    "app_dir = os.path.abspath(os.path.dirname(__file__))\r\n",
    "\r\n",
    "class BaseConfig:\r\n",
    "    SECRET_KEY = os.environ.get('SECRET_KEY') or 'A SECRET KEY'\r\n",
    "    SQLALCHEMY_TRACK_MODIFICATIONS = False\r\n",
    "\r\n",
    "    ##### Flask-Mail configurations #####\r\n",
    "    MAIL_SERVER = 'smtp.googlemail.com'\r\n",
    "    MAIL_PORT = 587\r\n",
    "    MAIL_USE_TLS = True\r\n",
    "    MAIL_USERNAME = os.environ.get('MAIL_USERNAME') or 'infooveriq@gmail.com'\r\n",
    "    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') or 'password'\r\n",
    "    MAIL_DEFAULT_SENDER = MAIL_USERNAME\r\n",
    "\r\n",
    "\r\n",
    "class DevelopementConfig(BaseConfig):\r\n",
    "    DEBUG = True\r\n",
    "    SQLALCHEMY_DATABASE_URI = os.environ.get('DEVELOPMENT_DATABASE_URI') or  \\\r\n",
    "        'mysql+pymysql://root:pass@localhost/flask_app_db'\r\n",
    "    \r\n",
    "\r\n",
    "class TestingConfig(BaseConfig):\r\n",
    "    DEBUG = True\r\n",
    "    SQLALCHEMY_DATABASE_URI = os.environ.get('TESTING_DATABASE_URI') or \\\r\n",
    "                              'mysql+pymysql://root:pass@localhost/flask_app_db'    \r\n",
    "\r\n",
    "class ProductionConfig(BaseConfig):\r\n",
    "    DEBUG = False\r\n",
    "    SQLALCHEMY_DATABASE_URI = os.environ.get('PRODUCTION_DATABASE_URI') or  \\\r\n",
    "        'mysql+pymysql://root:pass@localhost/flask_app_db'\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "注意，我们首次从环境变量中读取某些配置的值。如果环境变量没有设置，我们也会提供默认值。当您有一些不想在应用程序本身中硬编码的敏感数据时，此方法尤其有用。\r\n",
    "\r\n",
    "使用 from_object() 方法从类中读取配置，如下所示:\r\n",
    "\r\n",
    "`app.config.from_object('config.Create')`\r\n",
    "\r\n",
    "## 创建应用程序包\r\n",
    "\r\n",
    "在 flask_app 目录中创建一个新的目录 app 目录，并将所有文件和目录移动到这个目录(env 和迁移目录以及我们新创建的 config.py 文件除外)。在 app 目录中创建 `__init__.py` 文件，代码如下:\r\n",
    "\r\n",
    "flask_app/app/__init__.py\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask\r\n",
    "from flask_migrate import Migrate, MigrateCommand\r\n",
    "from flask_mail import Mail, Message\r\n",
    "from flask_sqlalchemy import SQLAlchemy\r\n",
    "from flask_script import Manager, Command, Shell\r\n",
    "from flask_login import LoginManager\r\n",
    "import os, config\r\n",
    "\r\n",
    "# create application instance\r\n",
    "app = Flask(__name__)\r\n",
    "app.config.from_object(os.environ.get('FLASK_ENV') or 'config.DevelopementConfig')\r\n",
    "\r\n",
    "# initializes extensions \r\n",
    "db = SQLAlchemy(app)\r\n",
    "mail = Mail(app)\r\n",
    "migrate = Migrate(app, db)\r\n",
    "login_manager = LoginManager(app)\r\n",
    "login_manager.login_view = 'login'\r\n",
    "\r\n",
    "# import views  \r\n",
    "from . import views  # 导入所有视图\r\n",
    "# from . import forum_views\r\n",
    "# from . import admin_views\r\n",
    "```\r\n",
    "\r\n",
    "`__init__.py` 创建应用程序实例并初始化扩展。 如果 `FLASK_env` 环境变量未设置，FLASK 应用程序将在调试模式下运行(即 app.debug = True)。 如果要将应用程序设置为生产模式，将 `FLASK_ENV` 环境变量设置为 `config.ProductionConfig`.\r\n",
    "\r\n",
    "初始化扩展之后，第21行的 import 语句将导入所有视图。这对于将应用程序实例连接到视图函数是必要的，否则，Flask 将不会知道您的视图函数。\r\n",
    "\r\n",
    "\r\n",
    "将 `main2.py` 文件重命名为 `views.py` 并对其进行更新，使其只包含路由和视图函数。下面是更新的 views.py 文件的完整代码。\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "**flask_app/app/views.py**\r\n",
    "\r\n",
    "```python\r\n",
    "from app import app\r\n",
    "from flask import render_template, request, redirect, url_for, flash, make_response, session\r\n",
    "from flask_login import login_required, login_user,current_user, logout_user\r\n",
    "from .models import User, Post, Category, Feedback, db\r\n",
    "from .forms import ContactForm, LoginForm\r\n",
    "from .utils import send_mail\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    return render_template('index.html', name='Jerry')\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/user/<int:user_id>/')\r\n",
    "def user_profile(user_id):\r\n",
    "    return \"Profile page of user #{}\".format(user_id)\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/books/<genre>/')\r\n",
    "def books(genre):\r\n",
    "    return \"All Books in {} category\".format(genre)\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/login/', methods=['post', 'get'])\r\n",
    "def login():\r\n",
    "    if current_user.is_authenticated:\r\n",
    "        return redirect(url_for('admin'))\r\n",
    "    form = LoginForm()\r\n",
    "    if form.validate_on_submit():\r\n",
    "        user = db.session.query(User).filter(User.username == form.username.data).first()\r\n",
    "        if user and user.check_password(form.password.data):\r\n",
    "            login_user(user, remember=form.remember.data)\r\n",
    "            return redirect(url_for('admin'))\r\n",
    "\r\n",
    "        flash(\"Invalid username/password\", 'error')\r\n",
    "        return redirect(url_for('login'))\r\n",
    "    return render_template('login.html', form=form)\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/logout/')\r\n",
    "@login_required\r\n",
    "def logout():\r\n",
    "    logout_user()\r\n",
    "    flash(\"You have been logged out.\")\r\n",
    "    return redirect(url_for('login'))\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/contact/', methods=['get', 'post'])\r\n",
    "def contact():\r\n",
    "    form = ContactForm()\r\n",
    "    if form.validate_on_submit():\r\n",
    "        name = form.name.data\r\n",
    "        email = form.email.data\r\n",
    "        message = form.message.data\r\n",
    "\r\n",
    "        # db logic goes here\r\n",
    "        feedback = Feedback(name=name, email=email, message=message)\r\n",
    "        db.session.add(feedback)\r\n",
    "        db.session.commit()\r\n",
    "\r\n",
    "        send_mail(\"New Feedback\", app.config['MAIL_DEFAULT_SENDER'], 'mail/feedback.html',\r\n",
    "                  name=name, email=email)\r\n",
    "\r\n",
    "        flash(\"Message Received\", \"success\")\r\n",
    "        return redirect(url_for('contact'))\r\n",
    "\r\n",
    "    return render_template('contact.html', form=form)\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/cookie/')\r\n",
    "def cookie():\r\n",
    "    if not request.cookies.get('foo'):\r\n",
    "        res = make_response(\"Setting a cookie\")\r\n",
    "        res.set_cookie('foo', 'bar', max_age=60*60*24*365*2)\r\n",
    "    else:\r\n",
    "        res = make_response(\"Value of cookie foo is {}\".format(request.cookies.get('foo')))\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/delete-cookie/')\r\n",
    "def delete_cookie():\r\n",
    "    res = make_response(\"Cookie Removed\")\r\n",
    "    res.set_cookie('foo', 'bar', max_age=0)\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/article', methods=['POST', 'GET'])\r\n",
    "def article():\r\n",
    "    if request.method == 'POST':\r\n",
    "        res = make_response(\"\")\r\n",
    "        res.set_cookie(\"font\", request.form.get('font'), 60*60*24*15)\r\n",
    "        res.headers['location'] = url_for('article')\r\n",
    "        return res, 302\r\n",
    "\r\n",
    "    return render_template('article.html')\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/visits-counter/')\r\n",
    "def visits():\r\n",
    "    if 'visits' in session:\r\n",
    "        session['visits'] = session.get('visits') + 1\r\n",
    "    else:\r\n",
    "        session['visits'] = 1\r\n",
    "    return \"Total visits: {}\".format(session.get('visits'))\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/delete-visits/')\r\n",
    "def delete_visits():\r\n",
    "    session.pop('visits', None) # delete visits\r\n",
    "    return 'Visits deleted'\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/session/')\r\n",
    "def updating_session():\r\n",
    "    res = str(session.items())\r\n",
    "\r\n",
    "    cart_item = {'pineapples': '10', 'apples': '20', 'mangoes': '30'}\r\n",
    "    if 'cart_item' in session:\r\n",
    "        session['cart_item']['pineapples'] = '100'\r\n",
    "        session.modified = True\r\n",
    "    else:\r\n",
    "        session['cart_item'] = cart_item\r\n",
    "\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@app.route('/admin/')\r\n",
    "@login_required\r\n",
    "def admin():\r\n",
    "    return render_template('admin.html')\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "views.py 文件现在只包含视图函数。我们已经将模型、表单类和实用函数的代码移动到它们各自的文件中，如下所示:"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "**flask_app/app/models.py**\r\n",
    "\r\n",
    "```python \r\n",
    "from app import db, login_manager\r\n",
    "from datetime import datetime\r\n",
    "from flask_login import (LoginManager, UserMixin, login_required,\r\n",
    "                          login_user, current_user, logout_user)\r\n",
    "from werkzeug.security import generate_password_hash, check_password_hash\r\n",
    "\r\n",
    "\r\n",
    "class Category(db.Model):\r\n",
    "    __tablename__ = 'categories'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    name = db.Column(db.String(255), nullable=False, unique=True)\r\n",
    "    slug = db.Column(db.String(255), nullable=False, unique=True)\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "    posts = db.relationship('Post', backref='category', cascade='all,delete-orphan')\r\n",
    "\r\n",
    "    def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(self.id, self.name)\r\n",
    "\r\n",
    "\r\n",
    "post_tags = db.Table('post_tags',\r\n",
    "    db.Column('post_id', db.Integer, db.ForeignKey('posts.id')),\r\n",
    "    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))\r\n",
    ")\r\n",
    "\r\n",
    "\r\n",
    "class Post(db.Model):\r\n",
    "    __tablename__ = 'posts'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    title = db.Column(db.String(255), nullable=False)\r\n",
    "    slug = db.Column(db.String(255), nullable=False)\r\n",
    "    content = db.Column(db.Text(), nullable=False)\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)\r\n",
    "    category_id = db.Column(db.Integer(), db.ForeignKey('categories.id'))\r\n",
    "\r\n",
    "    def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(self.id, self.title[:10])\r\n",
    "\r\n",
    "\r\n",
    "class Tag(db.Model):\r\n",
    "    __tablename__ = 'tags'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    name = db.Column(db.String(255), nullable=False)\r\n",
    "    slug = db.Column(db.String(255), nullable=False)\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "    posts = db.relationship('Post', secondary=post_tags, backref='tags')\r\n",
    "\r\n",
    "    def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(self.id, self.name)\r\n",
    "\r\n",
    "\r\n",
    "class Feedback(db.Model):\r\n",
    "    __tablename__ = 'feedbacks'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    name = db.Column(db.String(1000), nullable=False)\r\n",
    "    email = db.Column(db.String(100), nullable=False)\r\n",
    "    message = db.Column(db.Text(), nullable=False)\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "\r\n",
    "    def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(self.id, self.name)\r\n",
    "\r\n",
    "\r\n",
    "class Employee(db.Model):\r\n",
    "    __tablename__ = 'employees'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    name = db.Column(db.String(255), nullable=False)\r\n",
    "    designation = db.Column(db.String(255), nullable=False)\r\n",
    "    doj = db.Column(db.Date(), nullable=False)\r\n",
    "\r\n",
    "\r\n",
    "@login_manager.user_loader\r\n",
    "def load_user(user_id):\r\n",
    "    return db.session.query(User).get(user_id)\r\n",
    "\r\n",
    "\r\n",
    "class User(db.Model, UserMixin):\r\n",
    "    __tablename__ = 'users'\r\n",
    "    id = db.Column(db.Integer(), primary_key=True)\r\n",
    "    name = db.Column(db.String(100))\r\n",
    "    username = db.Column(db.String(50), nullable=False, unique=True)\r\n",
    "    email = db.Column(db.String(100), nullable=False, unique=True)\r\n",
    "    password_hash = db.Column(db.String(100), nullable=False)\r\n",
    "    created_on = db.Column(db.DateTime(), default=datetime.utcnow)\r\n",
    "    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)\r\n",
    "\r\n",
    "    def __repr__(self):\r\n",
    "        return \"<{}:{}>\".format(self.id, self.username)\r\n",
    "\r\n",
    "    def set_password(self, password):\r\n",
    "        self.password_hash = generate_password_hash(password)\r\n",
    "\r\n",
    "    def check_password(self, password):\r\n",
    "        return check_password_hash(self.password_hash, password)\r\n",
    "```\r\n",
    "        "
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "**flask_app/app/forms.py**\r\n",
    "\r\n",
    "```python\r\n",
    "from flask_wtf import FlaskForm\r\n",
    "from wtforms import Form, ValidationError\r\n",
    "from wtforms import StringField, SubmitField, TextAreaField, BooleanField\r\n",
    "from wtforms.validators import DataRequired, Email\r\n",
    "\r\n",
    "\r\n",
    "class ContactForm(FlaskForm):\r\n",
    "    name = StringField(\"Name: \", validators=[DataRequired()])\r\n",
    "    email = StringField(\"Email: \", validators=[Email()])\r\n",
    "    message = TextAreaField(\"Message\", validators=[DataRequired()])\r\n",
    "    submit = SubmitField()\r\n",
    "\r\n",
    "\r\n",
    "class LoginForm(FlaskForm):\r\n",
    "    username = StringField(\"Username\", validators=[DataRequired()])\r\n",
    "    password = StringField(\"Password\", validators=[DataRequired()])\r\n",
    "    remember = BooleanField(\"Remember Me\")\r\n",
    "    submit = SubmitField()\r\n",
    "```\r\n",
    "    "
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "**flask_app/app/utils.py**\r\n",
    "\r\n",
    "```python\r\n",
    "from . import mail, db\r\n",
    "from flask import render_template\r\n",
    "from threading import Thread\r\n",
    "from app import app\r\n",
    "from flask_mail import Message\r\n",
    "\r\n",
    "\r\n",
    "def async_send_mail(app, msg):\r\n",
    "    with app.app_context():\r\n",
    "        mail.send(msg)\r\n",
    "\r\n",
    "\r\n",
    "def send_mail(subject, recipient, template, **kwargs):\r\n",
    "    msg = Message(subject, sender=app.config['MAIL_DEFAULT_SENDER'], recipients=[recipient])\r\n",
    "    msg.html = render_template(template, **kwargs)\r\n",
    "    thrd = Thread(target=async_send_mail, args=[app, msg])\r\n",
    "    thrd.start()\r\n",
    "    return thrd\r\n",
    "```\r\n",
    "    "
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "最后，要启动应用程序，在 runner.py 文件中添加以下代码:"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "**flask_app/runner.py**\r\n",
    "```python \r\n",
    "import os\r\n",
    "from app import app, db\r\n",
    "from app.models import User, Post, Tag, Category, Employee, Feedback\r\n",
    "from flask_script import Manager, Shell\r\n",
    "from flask_migrate import MigrateCommand\r\n",
    "\r\n",
    "manager = Manager(app)\r\n",
    "\r\n",
    "# these names will be available inside the shell without explicit import\r\n",
    "def make_shell_context():\r\n",
    "    return dict(app=app,  db=db, User=User, Post=Post, Tag=Tag, Category=Category,\r\n",
    "                Employee=Employee, Feedback=Feedback)\r\n",
    "\r\n",
    "manager.add_command('shell', Shell(make_context=make_shell_context))\r\n",
    "manager.add_command('db', MigrateCommand)\r\n",
    "\r\n",
    "if __name__ == '__main__':\r\n",
    "    manager.run()\r\n",
    "```\r\n",
    "\r\n",
    "runner.py 是我们项目的入口点。该文件从创建 Manager()对象的实例开始。然后定义 `make_shell_context()` 函数。函数 `make_shell_context()` 返回的对象将在 shell 内部可用，不需要显式的 import 语句。最后，调用 `Manager` 实例上的 `run()` 方法来启动服务器。\r\n",
    "    \r\n",
    "    "
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Import 导入流程\r\n",
    "\r\n",
    "我们已经在本课中创建了相当多的文件，并且很容易忘记哪个文件做了什么以及执行文件的顺序。为了让事情更清楚，本节解释了所有事情是如何协同工作的。\r\n",
    "\r\n",
    "\r\n",
    "首先执行 runner.py 文件。 `runner.py` 文件的第二行从 `app` 包中导入 `app` 和 `db` 。当 Python 解释器遇到这一行时，程序控制转移到 `__init__.py` 开始执行。在`__init__.py`第7行导入`config.py` 的配置模块, 并将程序控制传输给它。当 `config.py` 的执行完成程序控制后，返回 `__init__.py`. 第21行，`__init__.py` 文件导入视图模块，该模块将程序控制传输到 `views.py`。`views.py` 的第一行再次从 `app` 包中导入应用程序实例 `app` 应用程序。应用程序实例 `app` 应用程序已经在内存中，因此不会再次导入。在第4、5和6行中，`views.py` 分别导入models模型、forms 表单和 `send_mail` 函数，这些函数依次将程序控制暂时转移到它们各自的文件中。当 `views.py` 的执行结束时，程序控制返回到 `__init__.py`.这样就完成了 `__init__.py` 的执行。程序控制返回 runner.py 并开始执行第3行中的语句。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "runner.py的第三行导入在 models.py 模块中定义的类。因为 views.py 中已经有了模型，所以不会再次执行 models.py 文件。\r\n",
    "\r\n",
    "由于我们将 runner.py 作为主模块运行，因此第17行中的条件计算结果为 True，而 manager.run() 启动应用程序。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 运行中的项目\r\n",
    "\r\n",
    "现在可以运行我们的项目了。在终端输入以下命令启动服务器。\r\n",
    "\r\n",
    "```python\r\n",
    "(env) overiq@vm:~/flask_app$ python runner.py runserver\r\n",
    " * Restarting with stat\r\n",
    " * Debugger is active!\r\n",
    " * Debugger PIN: 391-587-440\r\n",
    " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\r\n",
    "```\r\n",
    "\r\n",
    "\r\n",
    "如果没有设置 `FLASK_ENV` 环境变量，前面的命令将在调试模式下启动应用程序。导航到 http://127.0.0.1:5000/ ，你应该会看到主页，目前看起来是这样的:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/20/home_page-7bf44e8e-7bc8-45b3-826e-579c17d7bfd5.png)\r\n",
    "\r\n",
    "浏览应用程序的其余页面，确保一切按预期工作。\r\n",
    "\r\n",
    "我们的申请现在非常灵活。它可以通过阅读环境变量来获得一组完全不同的配置。例如，假设我们希望将站点置于生产模式。要做到这一点，只需创建一个配置环境变量 `FLASK_ENV` 值为 `config.ProductionConfig`.\r\n",
    "\r\n",
    "在终端中，输入以下命令\r\n",
    "\r\n",
    "### 创建 `FLASK_ENV` 环境变量:\r\n",
    "\r\n",
    "Linux 和 Mac os:  `export FLASK_ENV=config.ProductionConfig`\r\n",
    "Windows: `set FLASK_ENV=config.ProductionConfig`\r\n",
    "\r\n",
    "再次运行应用程序。\r\n",
    "\r\n",
    "```\r\n",
    "(env) overiq@vm:~/flask_app$ python runner.py runserver\r\n",
    " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\r\n",
    "```\r\n",
    "\r\n",
    "现在我们的应用程序正在生产模式下运行。此时，如果 Python 代码引发异常，您将看到一个500 Internal Server Error 而不是堆栈跟踪。\r\n",
    "\r\n",
    "因为我们仍处于开发阶段，我们不得不删除 `FLASK_ENV` 环境变量。当您关闭终端时，`FLASK_ENV` 将被自动删除。要手动删除它，输入以下命令:\r\n",
    "\r\n",
    "### 删除 `FLASK_ENV` 环境变量\r\n",
    "\r\n",
    "Linux 和 Mac os:  `unset FLASK_ENV`   \r\n",
    "windows: `set FLASK_ENV=`\r\n",
    "\r\n",
    "现在,我们的项目状况要好得多。事情的组织方式比以前更加可预测了。这里设计的技术对中小型项目是有用的。然而，`Flask `有一些更多的秘诀，可以帮助你变得更有效率。\r\n",
    "\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Blueprints 蓝图\r\n",
    "\r\n",
    "`Blueprints` 蓝图是组织应用程序的另一种方式。蓝图提供了视图层次的关注点分离。就像 Flask 应用程序一样，Blueprint 可以有自己的视图、静态文件和模板。我们也可以用它们自己的 URIs 来绘制蓝图。例如，假设我们正在处理一个博客及其管理面板。一个博客的蓝图将包含视图功能，模板和静态资产特定于博客。而管理面板的蓝图将包含特定于管理面板的视图、静态文件和模板。蓝图可以通过使用模块或包来实现。\r\n",
    "\r\n",
    "是时候给我们的项目增加一个蓝图了。\r\n",
    "\r\n",
    "## 创造蓝图\r\n",
    "\r\n",
    "在 `flask_app/app` 目录中创建一个名为 `main` 的目录，并将 `views.py` 和 `forms.py` 移动到此目录。在主目录中创建 `__init__.py` 文件，代码如下:\r\n",
    "\r\n",
    "flask_app/app/main/__init__.py\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Blueprint\r\n",
    "\r\n",
    "main = Blueprint('main', __name__)\r\n",
    "\r\n",
    "from . import views\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "我们正在使用 `Blueprint` 类创建 `Blueprint` 对象。`Blueprint()` 构造函数接受两个参数，即 Blueprint 名称和 Blueprint 所在的包的名称; 对于大多数应用程序来说，传递 `__name__` 给它就足够了。\r\n",
    "\r\n",
    "### 指定模板和静态资产的位置\r\n",
    "\r\n",
    "默认情况下，蓝图中的视图函数将分别在应用程序 `APP` 的模板目录 `templates` 和静态目录 `static` 中查找资源。\r\n",
    "\r\n",
    "我们可以通过在创建 Blueprint 对象时指定模板和静态资产的位置来改变这一点，如下所示:\r\n",
    "\r\n",
    "```python\r\n",
    "main = Blueprint('main', __name__\r\n",
    "                template_folder='templates_dir')\r\n",
    "                static_folder='static_dir')\r\n",
    "```\r\n",
    "\r\n",
    "蓝图添加的模板路径的优先级低于应用程序的模板目录。这意味着如果在 `templates_dir` 和 `templates` 目录中有两个同名的模板，Flask 将使用 `templates` 目录中的模板。\r\n",
    "\r\n",
    "### 关于这些蓝图，以下是一些值得注意的要点:\r\n",
    "\r\n",
    "1. 使用蓝图时，使用 blueprint 对象的 `route` 装饰器 而不是使用应用程序实例(app)定义路由。\r\n",
    "\r\n",
    "2. 使用蓝图时要创建 url，必须在端点前加上蓝图的名称和一个点(.).无论您是在 Python 代码中还是在模板中创建 url，这都是正确的。例如: `url_for(\"main.index\")`. 这将返回主蓝图索引路线的 URL。蓝图的名称可以省略，当您位于的是要为其创建 URL 的对象的同一个蓝图。例如: `url_for(\".index\")` 这将返回主蓝图索引路线的 URL，假设您位于主蓝图的视图函数或模板中。\r\n",
    "\r\n",
    "为了适应必须进行的更改，我们必须更新 import 语句、 `url_for()` 调用和 views.py 文件中的路由。下面是 views.py 文件的更新版本。\r\n",
    "\r\n",
    "### flask_app/app/main/views.py\r\n",
    "\r\n",
    "```python\r\n",
    "from app import app, db\r\n",
    "from . import main\r\n",
    "from flask import Flask, request, render_template, redirect, url_for, flash, make_response, session\r\n",
    "from flask_login import login_required, login_user, current_user, logout_user\r\n",
    "from app.models import User, Post, Category, Feedback, db\r\n",
    "from .forms import ContactForm, LoginForm\r\n",
    "from app.utils import send_mail\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/')\r\n",
    "def index():\r\n",
    "    return render_template('index.html', name='Jerry')\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/user/<int:user_id>/')\r\n",
    "def user_profile(user_id):\r\n",
    "    return \"Profile page of user #{}\".format(user_id)\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/books/<genre>/')\r\n",
    "def books(genre):\r\n",
    "    return \"All Books in {} category\".format(genre)\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/login/', methods=['post', 'get'])\r\n",
    "def login():\r\n",
    "    if current_user.is_authenticated:\r\n",
    "        return redirect(url_for('.admin'))\r\n",
    "    form = LoginForm()\r\n",
    "    if form.validate_on_submit():\r\n",
    "        user = db.session.query(User).filter(User.username == form.username.data).first()\r\n",
    "        if user and user.check_password(form.password.data):\r\n",
    "            login_user(user, remember=form.remember.data)\r\n",
    "            return redirect(url_for('.admin'))\r\n",
    "\r\n",
    "        flash(\"Invalid username/password\", 'error')\r\n",
    "        return redirect(url_for('.login'))\r\n",
    "    return render_template('login.html', form=form)\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/logout/')\r\n",
    "@login_required\r\n",
    "def logout():\r\n",
    "    logout_user()    \r\n",
    "    flash(\"You have been logged out.\")\r\n",
    "    return redirect(url_for('.login'))\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/contact/', methods=['get', 'post'])\r\n",
    "def contact():\r\n",
    "    form = ContactForm()\r\n",
    "    if form.validate_on_submit():\r\n",
    "        name = form.name.data\r\n",
    "        email = form.email.data\r\n",
    "        message = form.message.data\r\n",
    "        print(name)\r\n",
    "        print(email)\r\n",
    "        print(message)\r\n",
    "                \r\n",
    "        # db logic goes here\r\n",
    "        feedback = Feedback(name=name, email=email, message=message)\r\n",
    "        db.session.add(feedback)\r\n",
    "        db.session.commit()\r\n",
    "        \r\n",
    "        send_mail(\"New Feedback\", app.config['MAIL_DEFAULT_SENDER'], 'mail/feedback.html',\r\n",
    "                  name=name, email=email)\r\n",
    "        \r\n",
    "        print(\"\\nData received. Now redirecting ...\")\r\n",
    "        flash(\"Message Received\", \"success\")\r\n",
    "        return redirect(url_for('.contact'))\r\n",
    "\r\n",
    "    return render_template('contact.html', form=form)\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/cookie/')\r\n",
    "def cookie():\r\n",
    "    if not request.cookies.get('foo'):\r\n",
    "        res = make_response(\"Setting a cookie\")\r\n",
    "        res.set_cookie('foo', 'bar', max_age=60*60*24*365*2)\r\n",
    "    else:\r\n",
    "        res = make_response(\"Value of cookie foo is {}\".format(request.cookies.get('foo')))\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/delete-cookie/')\r\n",
    "def delete_cookie():\r\n",
    "    res = make_response(\"Cookie Removed\")\r\n",
    "    res.set_cookie('foo', 'bar', max_age=0)\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/article/', methods=['POST', 'GET'])\r\n",
    "def article():\r\n",
    "    if request.method == 'POST':\r\n",
    "        print(request.form)\r\n",
    "        res = make_response(\"\")\r\n",
    "        res.set_cookie(\"font\", request.form.get('font'), 60*60*24*15)\r\n",
    "        res.headers['location'] = url_for('.article')\r\n",
    "        return res, 302\r\n",
    "    \r\n",
    "    return render_template('article.html')\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/visits-counter/')\r\n",
    "def visits():\r\n",
    "    if 'visits' in session:\r\n",
    "        session['visits'] = session.get('visits') + 1  # reading and updating session data\r\n",
    "    else:\r\n",
    "        session['visits'] = 1 # setting session data\r\n",
    "    return \"Total visits: {}\".format(session.get('visits'))\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/delete-visits/')\r\n",
    "def delete_visits():\r\n",
    "    session.pop('visits', None) # delete visits\r\n",
    "    return 'Visits deleted'\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/session/')\r\n",
    "def updating_session():\r\n",
    "    res = str(session.items())\r\n",
    "\r\n",
    "    cart_item = {'pineapples': '10', 'apples': '20', 'mangoes': '30'}\r\n",
    "    if 'cart_item' in session:\r\n",
    "        session['cart_item']['pineapples'] = '100'\r\n",
    "        session.modified = True\r\n",
    "    else:\r\n",
    "        session['cart_item'] = cart_item\r\n",
    "\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/admin/')\r\n",
    "@login_required\r\n",
    "def admin():\r\n",
    "    return render_template('admin.html')\r\n",
    "```\r\n",
    "\r\n",
    "注意，在 views.py 文件中，我们创建 URL 时没有指定蓝图名称，因为我们处于为其创建 URL 的同一个蓝图中。\r\n",
    "\r\n",
    "同时在 admin.html 中更新如下的 `url_for()` 调用(logout前加了一个点.) :\r\n",
    "\r\n",
    "### flask_app/app/templates/admin.html\r\n",
    "```html \r\n",
    "#...\r\n",
    "<p><a href=\"{{ url_for('.logout') }}\">Logout</a></p>\r\n",
    "#...\r\n",
    "```\r\n",
    "\r\n",
    "views.py 中的视图函数现在与主蓝图关联。接下来，我们必须在 Flask 应用程序上注册蓝图。打开 app/__init__.py 并修改它如下: (突出显示更改) :\r\n",
    "\r\n",
    "### flask_app/app/__init__.py\r\n",
    "\r\n",
    "```python\r\n",
    "\r\n",
    "#...\r\n",
    "# create application instance\r\n",
    "app = Flask(__name__)\r\n",
    "app.config.from_object(os.environ.get('FLASK_ENV') or 'config.DevelopementConfig')\r\n",
    "\r\n",
    "# initializes extensions\r\n",
    "db = SQLAlchemy(app)\r\n",
    "mail = Mail(app)\r\n",
    "migrate = Migrate(app, db)\r\n",
    "login_manager = LoginManager(app)  \r\n",
    "login_manager.login_view = 'main.login'  # 以下修改\r\n",
    "\r\n",
    "# register blueprints\r\n",
    "from .main import main as main_blueprint\r\n",
    "app.register_blueprint(main_blueprint)\r\n",
    "\r\n",
    "#from .admin import main as admin_blueprint\r\n",
    "#app.register_blueprint(admin_blueprint)\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "应用程序实例的 `register_blueprint()` 方法用于注册 `blueprint`。我们可以通过为每个蓝图调用 `register_blueprint()` 来注册多个蓝图。注意，在第11行中，我们将 main.login 分配给 `login_manager.login_view` 登录视图。在这种情况下，有必要指定蓝图名称(main)，否则 Flask 将无法明白您所指的是哪个蓝图。\r\n",
    "\r\n",
    "### 现在，应用程序结构应该是这样的:\r\n",
    "\r\n",
    "```\r\n",
    "├── flask_app/\r\n",
    "├── app/\r\n",
    "│   ├── __init__.py\r\n",
    "│   ├── main/\r\n",
    "│   │   ├── forms.py\r\n",
    "│   │   ├── __init__.py\r\n",
    "│   │   └── views.py\r\n",
    "│   ├── models.py\r\n",
    "│   ├── static/\r\n",
    "│   │   └── style.css\r\n",
    "│   ├── templates/\r\n",
    "│   │   ├── admin.html\r\n",
    "│   │   ├── article.html\r\n",
    "│   │   ├── contact.html\r\n",
    "│   │   ├── index.html\r\n",
    "│   │   ├── login.html\r\n",
    "│   │   └── mail/\r\n",
    "│   │       └── feedback.html\r\n",
    "│   └── utils.py\r\n",
    "├── migrations/\r\n",
    "│   ├── alembic.ini\r\n",
    "│   ├── env.py\r\n",
    "│   ├── README\r\n",
    "│   ├── script.py.mako\r\n",
    "│   └── versions/\r\n",
    "│       ├── 0f0002bf91cc_adding_users_table.py\r\n",
    "│       ├── 6e059688f04e_adding_employees_table.py\r\n",
    "├── runner.py\r\n",
    "├── config.py\r\n",
    "├── env/\r\n",
    "```\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 应用工厂\r\n",
    "\r\n",
    "我们已经在应用程序中使用了包和蓝图。我们可以通过将实例化应用程序实例的任务委派给应用工厂 `Application Factory` 来进一步改进我们的应用程序。应用工厂 `Application Factory` 只是一个创建对象的函数。\r\n",
    "\r\n",
    "那么我们这样做能得到什么呢:\r\n",
    "\r\n",
    "1. 它使测试更容易，因为我们可以创建具有不同设置的应用程序实例\r\n",
    "\r\n",
    "2. 我们可以在同一个进程中运行同一个应用程序的多个实例。当负载均衡器在不同的服务器之间分配流量时，这非常方便\r\n",
    "\r\n",
    "让我们更新 `app/__init__.py` 来实现一个应用程序工厂，如下所示(突出显示更改) :\r\n",
    "\r\n",
    "### flask_app/app/__init__.py\r\n",
    "```python\r\n",
    "from flask import Flask\r\n",
    "from flask_migrate import Migrate, MigrateCommand\r\n",
    "from flask_mail import Mail, Message\r\n",
    "from flask_sqlalchemy import SQLAlchemy\r\n",
    "from flask_script import Manager, Command, Shell\r\n",
    "from flask_login import LoginManager\r\n",
    "import os, config\r\n",
    "\r\n",
    "db = SQLAlchemy()  # 以下全部更改\r\n",
    "mail = Mail()\r\n",
    "migrate = Migrate()\r\n",
    "login_manager = LoginManager()\r\n",
    "login_manager.login_view = 'main.login'\r\n",
    "\r\n",
    "# application factory  应用程序工厂\r\n",
    "def create_app(config):\r\n",
    "    \r\n",
    "    # create application instance 创建一个应用实例\r\n",
    "    app = Flask(__name__)\r\n",
    "    app.config.from_object(config)\r\n",
    "\r\n",
    "    db.init_app(app)\r\n",
    "    mail.init_app(app)\r\n",
    "    migrate.init_app(app, db)\r\n",
    "    login_manager.init_app(app)\r\n",
    "\r\n",
    "    from .main import main as main_blueprint\r\n",
    "    app.register_blueprint(main_blueprint)\r\n",
    "\r\n",
    "    #from .admin import main as admin_blueprint\r\n",
    "    #app.register_blueprint(admin_blueprint)\r\n",
    "\r\n",
    "    return app\r\n",
    "```\r\n",
    "\r\n",
    "我们已经将创建应用程序实例的任务委托给 create_app() 函数。`create_app()` 函数接受一个名为 `config` 的参数，并返回一个应用程序实例。\r\n",
    "\r\n",
    "应用程序工厂将扩展们的 实例化`instantiation` 与 配置`configurations` 分离。实例化发生在`create_app()`函数被调用前, 实例的配置在`create_app()`函数内部完成, 通过使用扩展们的 `init_app()` 方法来完成配置。\r\n",
    "\r\n",
    "接下来，更新 runner.py 使用 applicationfactory 如下:\r\n",
    "\r\n",
    "### flask_app/runner.py\r\n",
    "```python\r\n",
    "import os\r\n",
    "from app import db, create_app\r\n",
    "from app.models import User, Post, Tag, Category, Employee, Feedback\r\n",
    "from flask_script import Manager, Shell\r\n",
    "from flask_migrate import MigrateCommand\r\n",
    "\r\n",
    "app = create_app(os.getenv('FLASK_ENV') or 'config.DevelopementConfig')\r\n",
    "manager = Manager(app)\r\n",
    "\r\n",
    "def make_shell_context():\r\n",
    "    return dict(app=app,  db=db, User=User, Post=Post, Tag=Tag, Category=Category,\r\n",
    "                Employee=Employee, Feedback=Feedback)\r\n",
    "\r\n",
    "manager.add_command('shell', Shell(make_context=make_shell_context))\r\n",
    "manager.add_command('db', MigrateCommand)\r\n",
    "\r\n",
    "if __name__ == '__main__':\r\n",
    "    manager.run()\r\n",
    "```\r\n",
    "\r\n",
    "需要注意的是，在使用应用程序工厂时，我们在导入时不再能够访问蓝图中的应用程序实例。要访问蓝图中的应用程序，可以使用来自 flask 包的 `current_app` 代理。让我们使用 `current_app`  变量更新我们的项目如下:\r\n",
    "\r\n",
    "### flask_app/app/main/views.py\r\n",
    "\r\n",
    "```python\r\n",
    "from app import db\r\n",
    "from . import main\r\n",
    "from flask import (render_template, request, redirect, url_for, flash,\r\n",
    "                   make_response, session, current_app)  # 使用 `current_app` 变量\r\n",
    "from flask_login import login_required, login_user, current_user, logout_user\r\n",
    "from app.models import User, Feedback\r\n",
    "from app.utils import send_mail\r\n",
    "from .forms import ContactForm, LoginForm\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/')\r\n",
    "def index():\r\n",
    "    return render_template('index.html', name='Jerry')\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/user/<int:user_id>/')\r\n",
    "def user_profile(user_id):\r\n",
    "    return \"Profile page of user #{}\".format(user_id)\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/books/<genre>/')\r\n",
    "def books(genre):\r\n",
    "    return \"All Books in {} category\".format(genre)\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/login/', methods=['post', 'get'])\r\n",
    "def login():\r\n",
    "    if current_user.is_authenticated:\r\n",
    "        return redirect(url_for('.admin'))\r\n",
    "    form = LoginForm()\r\n",
    "    if form.validate_on_submit():\r\n",
    "        user = db.session.query(User).filter(User.username == form.username.data).first()\r\n",
    "        if user and user.check_password(form.password.data):\r\n",
    "            login_user(user, remember=form.remember.data)\r\n",
    "            return redirect(url_for('.admin'))\r\n",
    "\r\n",
    "        flash(\"Invalid username/password\", 'error')\r\n",
    "        return redirect(url_for('.login'))\r\n",
    "    return render_template('login.html', form=form)\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/logout/')\r\n",
    "@login_required\r\n",
    "def logout():\r\n",
    "    logout_user()\r\n",
    "    flash(\"You have been logged out.\")\r\n",
    "    return redirect(url_for('.login'))\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/contact/', methods=['get', 'post'])\r\n",
    "def contact():\r\n",
    "    form = ContactForm()\r\n",
    "    if form.validate_on_submit():\r\n",
    "        name = form.name.data\r\n",
    "        email = form.email.data\r\n",
    "        message = form.message.data\r\n",
    "\r\n",
    "        # db logic goes here\r\n",
    "        feedback = Feedback(name=name, email=email, message=message)\r\n",
    "        db.session.add(feedback)\r\n",
    "        db.session.commit()\r\n",
    "\r\n",
    "        send_mail(\"New Feedback\", current_app.config['MAIL_DEFAULT_SENDER'], 'mail/feedback.html',\r\n",
    "                  name=name, email=email)  # current_app.config\r\n",
    "\r\n",
    "        flash(\"Message Received\", \"success\")\r\n",
    "        return redirect(url_for('.contact'))\r\n",
    "\r\n",
    "    return render_template('contact.html', form=form)\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/cookie/')\r\n",
    "def cookie():\r\n",
    "    if not request.cookies.get('foo'):\r\n",
    "        res = make_response(\"Setting a cookie\")\r\n",
    "        res.set_cookie('foo', 'bar', max_age=60*60*24*365*2)\r\n",
    "    else:\r\n",
    "        res = make_response(\"Value of cookie foo is {}\".format(request.cookies.get('foo')))\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/delete-cookie/')\r\n",
    "def delete_cookie():\r\n",
    "    res = make_response(\"Cookie Removed\")\r\n",
    "    res.set_cookie('foo', 'bar', max_age=0)\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/article', methods=['POST', 'GET'])\r\n",
    "def article():\r\n",
    "    if request.method == 'POST':\r\n",
    "        res = make_response(\"\")\r\n",
    "        res.set_cookie(\"font\", request.form.get('font'), 60*60*24*15)\r\n",
    "        res.headers['location'] = url_for('.article')\r\n",
    "        return res, 302\r\n",
    "\r\n",
    "    return render_template('article.html')\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/visits-counter/')\r\n",
    "def visits():\r\n",
    "    if 'visits' in session:\r\n",
    "        session['visits'] = session.get('visits') + 1\r\n",
    "    else:\r\n",
    "        session['visits'] = 1\r\n",
    "    return \"Total visits: {}\".format(session.get('visits'))\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/delete-visits/')\r\n",
    "def delete_visits():\r\n",
    "    session.pop('visits', None) # delete visits\r\n",
    "    return 'Visits deleted'\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/session/')\r\n",
    "def updating_session():\r\n",
    "    res = str(session.items())\r\n",
    "\r\n",
    "    cart_item = {'pineapples': '10', 'apples': '20', 'mangoes': '30'}\r\n",
    "    if 'cart_item' in session:\r\n",
    "        session['cart_item']['pineapples'] = '100'\r\n",
    "        session.modified = True\r\n",
    "    else:\r\n",
    "        session['cart_item'] = cart_item\r\n",
    "\r\n",
    "    return res\r\n",
    "\r\n",
    "\r\n",
    "@main.route('/admin/')\r\n",
    "@login_required\r\n",
    "def admin():\r\n",
    "    return render_template('admin.html')\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "### flask_app/app/utils.py\r\n",
    "\r\n",
    "```python\r\n",
    "from . import mail, db\r\n",
    "from flask import render_template, current_app\r\n",
    "from threading import Thread\r\n",
    "from flask_mail import Message\r\n",
    "\r\n",
    "\r\n",
    "def async_send_mail(app, msg):\r\n",
    "    with app.app_context():\r\n",
    "        mail.send(msg)\r\n",
    "\r\n",
    "\r\n",
    "def send_mail(subject, recipient, template, **kwargs):\r\n",
    "    msg = Message(subject, sender=current_app.config['MAIL_DEFAULT_SENDER'], recipients=[recipient])\r\n",
    "    msg.html = render_template(template, **kwargs)\r\n",
    "    thr = Thread(target=async_send_mail, args=[current_app._get_current_object(), msg])\r\n",
    "    thr.start()\r\n",
    "    return thr\r\n",
    "```    "
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "现在你应该对Flask和它的不同组件以及所有东西如何组合在一起有了一个直观的理解。信不信由你，我们已经探索了很多。在本课程的下一部分，我们将使用我们所学到的创建一个美味的克隆，名叫`Flask-Marks`。 `Delicious` 是2003年推出的社会性书签网站。在2017年，它被 Pinboard 收购，从那时起它就以只读模式运行。翻页，我们开始吧。\r\n"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}